package com.meshnetics.gb.type.impl;

import com.meshnetics.gb.type.DataType;
import com.meshnetics.gb.type.TypesRegistry;
import com.meshnetics.gb.GBProperties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * Global project TypesRegistry. Allows to register types.
 *
 * @author AKovylin
 */
public final class GlobalFinalTypesRegistry implements TypesRegistry {
    // numbers should be choosed according to number of types*aliases we will have to avoid rehash
    private final Map byName = new HashMap((int) (512 / 0.75f) + 1, 0.75f); // String -> DataType
    // as dataTypeId is byte - 256 values maximum can be in this hash, to avoid rehash
    private final Map byId = new HashMap((int) (256 / 0.75f) + 1, 0.75f);   // Byte -> DataType

    public final static GlobalFinalTypesRegistry INSTANCE = new GlobalFinalTypesRegistry();

    /**
     * Warkaround here. Call of {@link #registerType(com.meshnetics.gb.type.DataType)} is performed in constructors
     * of types now. Types are static fields of corresponding classes. And if types registry class is loaded and used
     * before type classes - no types registered in global registry. Local resource file is used "initList.txt", each
     * line of file is treated as name of class to load with initialization.
     */
    static {
        GBProperties.init();
        Log log = LogFactory.getLog(GlobalFinalTypesRegistry.class);
        /*
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(
                    GlobalFinalTypesRegistry.class.getResourceAsStream("initList.txt")));
            for (String s = br.readLine(); s != null; s = br.readLine()) {
                try {
                    Class.forName(s);
                } catch (ClassNotFoundException e) {
                    log.error("initialization error for class " + s, e);
                }
            }


        } catch (IOException e) {
            log.error("initialization failed", e);
        }
        //*/
        try{
            Class.forName("com.meshnetics.gb.type.impl.zcl.TypeBitmap");
            Class.forName("com.meshnetics.gb.type.impl.zcl.TypeBool");
            Class.forName("com.meshnetics.gb.type.impl.zcl.TypeEnumeration");
            Class.forName("com.meshnetics.gb.type.impl.zcl.TypeGeneralData");
            Class.forName("com.meshnetics.gb.type.impl.zcl.TypeInt");
            Class.forName("com.meshnetics.gb.type.impl.zcl.TypeNull");
            Class.forName("com.meshnetics.gb.type.impl.zcl.TypeStr");
            Class.forName("com.meshnetics.gb.type.impl.zcl.TypeUInt");
            Class.forName("com.meshnetics.gb.type.impl.zcl.TypeTime");
            Class.forName("com.meshnetics.gb.stack.mac.type.TypeChannelMask");
            //Class.forName("com.meshnetics.gb.stack.aps.binding.BindTable");          // what for ???

        }catch(Exception ex){
            log.error("initialization failed", ex);
        }
    }

    private GlobalFinalTypesRegistry() {
        //System.out.println("I'm here!!!");
    }

    public void registerType(DataType dataType) {
        if (dataType == null) {
            throw new NullPointerException("dataType is null");
        }
        Byte bKey = dataType.getDataTypeID() == null ? null : new Byte(dataType.getDataTypeID().getDataTypeId());
        String sKey = dataType.getName();
        if (sKey == null) {
            throw new NullPointerException("dataType name is null");
        }
        synchronized (byId) {
            if (bKey != null && byId.containsKey(bKey)) {
                throw new IllegalArgumentException("type with code " + StringUtils.toHexString(bKey.byteValue()) +
                        "already registered");
            }
            if (byName.get(sKey) != null) {
                throw new IllegalArgumentException("type with name '" + sKey + "' already registered");
            }
            String[] aliases = dataType.getAliases();
            for (int i = 1; i < aliases.length; i++) { // zeroth element must be main name!
                if (aliases[i] != null && byName.get(sKey) != null) {
                    throw new IllegalArgumentException("type with name '" + aliases[i] + "' already registered");
                }
            }
            if (bKey != null) {
                byId.put(bKey, dataType);
            }
            byName.put(sKey, dataType);
            for (int i = 1; i < aliases.length; i++) { // zeroth element must be main name!
                if (aliases[i] != null) {
                    byName.put(aliases[i], dataType);
                }
            }
        }
    }

    public DataType[] getRegisteredTypes() {
        synchronized (byId) {
            DataType[] res = new DataType[byName.size()];
            int n = 0;
            Iterator it = byName.values().iterator();
            while (it.hasNext()) {
                res[n++] = (DataType) it.next();
            }
            return res;
        }
    }

    public DataType getType(byte dataTypeID) {
        synchronized (byId) {
            return (DataType) byId.get(new Byte(dataTypeID));
        }
    }

    public DataType getType(String name) {
        if (name != null) {
            synchronized (byId) {
                return (DataType) byName.get(name);
            }
        } else {
            return null;
        }
    }
}
